﻿using System;
using System.Collections.Generic;
using System.Linq;

/* 
 * Compact
    英 [kəmˈpækt]   美 [ˈkɑ:mˈpækt]  
    vt.& vi.
    压紧，（使）坚实;把…弄紧密，把…弄结实;使（文体）简洁，简化;变紧密，变结实
    adj.
    紧凑的;简洁的，（文体等）紧凑的;小巧易携带的;（物质）致密的，（体格）结实的
    n.
    协议;条约;带镜小粉盒;小汽车
 */
namespace LibMQTT.ProtocalIMPL
{
    using System.Text;
    using MrPoetic.MatLab;
    using TCPServices;

    /// 丁诚昊对 IBM公司物联网Lot协议 MQTT的具体实现 版本号为 V3.1
    ///  这个命名空间是对IBM MQTT物联网协议Lot的简单实现 
    ///  MQTT盲僧: 
    ///     0.固定报头 包含2个部分: 标识位(B1) + 剩余长度(B2)[需要计算]
    ///     1.可1变报头 包含 1.协议名 2.协议级别 3.连接标识 4.保持连接
    ///     2.有效载荷PayLoad: 主要是用于立遗嘱的
    public class MqCompact
    {
        // 1区 固定报文
        public byte[] H1Stream { get { return _FixedH1.ToArray(); } }

        // 合并 2区Vari + 3区Payload 进行Socket二次发送
        public byte[] H23Stream {
            get
            {
                _VariableH2.AddRange(_PayloadH3ToN);
                _PayloadH3ToN.Clear();
                return _VariableH2.ToArray<byte>();
            }
        }
        
        // `FixedHeader 首次数据报文` 
        Queue<byte> _FixedH1 = new Queue<byte>(1);
        // `Variable + Payload(标识位) 可变报文: 可变数据报文+有效载荷标识位`
        List<byte> _VariableH2 = new List<byte>(0);
        // `PayloadData有效载荷` 第三部分内容有效值根据H2中的标识位进行荷载
        List<byte> _PayloadH3ToN = new List<byte>(0);

        private MqCompact() { }
        /// 调用 `MQTT函数`- <生成固定报头>
        public MqCompact(IBMComPacketType HType,IEnumerable<byte> AppMsg = null)
            : this()
        {
            // 1.生成MQTT固定报文 1区
            MakeHFix1(AppMsg,HType);
        }
        public void MakeHFix1(IEnumerable<byte> AppMsg, IBMComPacketType type)
        {
            byte b1 = MqLotLambda._GenFixH1(type);
            // 1.转码报文类型 +1字节
            _FixedH1.Enqueue(b1);
            switch (type)
            {
                case IBMComPacketType.PingReq:
                case IBMComPacketType.PingResp:
                case IBMComPacketType.DisConnect:
                    break;
                case IBMComPacketType.Connect:
                case IBMComPacketType.Publish:
                case IBMComPacketType.SubScribe:
                    GenVariPack4Conn();
                    GenPayload(AppMsg);
                    /* 注意: 必须先生成23区,再计算数据长度1区的2~5字节 */
                    byte[] MQTTLen = MqLotLambda._GenVLen(H23Stream);
                    int count = 0;
                    while(count < MQTTLen.Length)
                        _FixedH1.Enqueue(MQTTLen[count++]);
                    break;
            }
        }

        public void GenVariPack4Conn()
        {
            /// 丁者诚昊作于天朝82年
            var protocalName = "YiXinDa";
            var PNameSerial = Encoding.UTF8.GetBytes(protocalName);
            byte MSB = 0x00;
            byte LSB = Convert.ToByte(PNameSerial.Length);
            _VariableH2.Add(MSB);                          // 1.字节
            _VariableH2.Add(LSB);                          // 2.字节
            _VariableH2.AddRange(PNameSerial);             // 6.字节
            _VariableH2.Add(Convert.ToByte(12));         // 7.字节 协议等级 16进制的`31`

            // 2.协议级别 至少1个字节 8Bit电荷位
            var CONNECTFLAGS = 0;
            CONNECTFLAGS += 1 << 7; // 用户名        Username
            CONNECTFLAGS += 1 << 6; // 密码          Password
            CONNECTFLAGS += 0 << 5; // 遗嘱保留位    WillRetain
            CONNECTFLAGS += 0 << 4; // 遗嘱QoS1      WillQoS
            CONNECTFLAGS += 1 << 3; // 遗嘱QoS2      WillQoS      
            CONNECTFLAGS += 1 << 2; // 遗嘱标识位    WillFlag
            CONNECTFLAGS += 0 << 1; // 会话标识位    Session
            CONNECTFLAGS += 0 << 0; // 保留位
            var b = Convert.ToByte(CONNECTFLAGS);
            _VariableH2.Add(b);                            // 8.字节

            // 16个电荷位构成剩余最后2字节 :保持连接[MSB][LSB]: 9.字节 + 10.字节
            // SynWait会话的有效时间 默认20分钟
            var KeepAliveInterVal = 60 * 5;  
            var times = BitConverter.GetBytes(KeepAliveInterVal);
            for (int i = 0; i < 2; i++)
            {
                _VariableH2.Add(times[i]);
            }
            // 返回可变报文最终长度 通常为10字节
        }
        public void GenPayload(IEnumerable<byte> AppMsg)
        {
            string ClientID = DataMatrix.GenAnyLetters(23);
            byte[] s0 = blockCoder.GetBytes(ClientID);
            _PayloadH3ToN.Add(Convert.ToByte(s0.Length));
            _PayloadH3ToN.AddRange(s0);

            // WillTopic
            var WillTopic = "/UserControl/Offline?token=丁者诚昊";
            byte[] s1 = blockCoder.GetBytes(WillTopic);
            _PayloadH3ToN.Add(Convert.ToByte(s1.Length));
            _PayloadH3ToN.AddRange(s1);

            // WillMessage
            var WillMessage = string.Format("用户下线了~");
            byte[] s2 = blockCoder.GetBytes(WillMessage);
            _PayloadH3ToN.Add(Convert.ToByte(s2.Length));
            _PayloadH3ToN.AddRange(s2);

            // AttachedMsg
            _PayloadH3ToN.Add(Convert.ToByte(AppMsg.Count()));
            _PayloadH3ToN.AddRange(AppMsg);

            // 随便丢点什么东西进去
            var infosAppending = new string[] {
                "载荷长度[6]:[6]/剩余长度 --->0",
                "YiXinDa" };
            infosAppending.ToList().ForEach(values => {
                byte[] codes = blockCoder.GetBytes(values);
                byte N = Convert.ToByte(codes.Length);
                _PayloadH3ToN.Add(N);
                _PayloadH3ToN.AddRange(codes);
            });
#if !DEBUG
            Console.WriteLine("DoPayload 消息发送方检查Payload报文");
            _PayloadH3ToN.ToList().ForEach(b => Console.Write("[{0:x}].",b));
            Console.WriteLine();
#endif
        }
        internal static Encoding blockCoder = MqLotBase._BlockCoder;
    }

    public enum IBMComPacketType
    {
        /* 0|15位 为系统保留字 */
        /* 连接 */
        Connect = 1, ConnAck,                       
        /* MQTT 消息 */
        Publish, PubAck, PubRec, PubREL,PubComp,    
        /* MQTT 订阅 */
        SubScribe, SubBack, UnSubScribe, UnSuback,  
        /* MQTT 心跳包 + 主动断开 */
        PingReq, PingResp, DisConnect,                                 
    }
}
